package com.ruoyi.web.controller.wx.request;

import com.ruoyi.common.exception.ServiceException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.data.repository.init.ResourceReader;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Random;

/**
 * @author kaur
 */
@Slf4j
public class WxPayV3Util {
    /**
     * @param method       请求方法 post
     * @param canonicalUrl 请求地址
     * @param body         请求参数   GET请求传空字符
     * @param merchantId   这里用的商户号
     * @param certSerialNo 商户证书序列号
     * @param keyPath      私钥商户证书地址
     * @return String
     * @throws Exception 异常信息
     */
    public static String getToken(String method, String canonicalUrl, String body,
                                  String merchantId, String certSerialNo, String keyPath) throws Exception {
        String signStr;
        //获取32位随机字符串
        String nonceStr = getRandomString(32);
        //当前系统运行时间
        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(method, canonicalUrl, timestamp, nonceStr, body);
        //签名操作
        String signature = sign(message.getBytes(StandardCharsets.UTF_8), keyPath);
        //组装参数
        signStr = "mchid=\"" + merchantId + "\"," +
                "nonce_str=\"" + nonceStr + "\"," +
                "timestamp=\"" + timestamp + "\"," +
                "serial_no=\"" + certSerialNo + "\"," +
                "signature=\"" + signature + "\"";
        return signStr;
    }

    public static String buildMessage(String method, String canonicalUrl, long timestamp, String nonceStr, String body) {
        return method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n";
    }

    public static String sign(byte[] message, String keyPath) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(keyPath));
        sign.update(message);
        return Base64.encodeBase64String(sign.sign());
    }

    /**
     * 微信支付-前端唤起支付参数-获取商户私钥
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    @SneakyThrows
    public static PrivateKey getPrivateKey(String filename) {
        WxPayV3Util wxPayV3Util = new WxPayV3Util();
        String content = wxPayV3Util.readFileContent(filename);
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new ServiceException("当前Java环境不支持RSA" + e.getMessage());
        } catch (InvalidKeySpecException e) {
            throw new ServiceException("无效的密钥格式");
        }
    }

    /**
     * 获取随机位数的字符串
     *
     * @param length 需要的长度
     * @return String
     */
    public static String getRandomString(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuilder stringBuffer = new StringBuilder();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            stringBuffer.append(base.charAt(number));
        }
        return stringBuffer.toString();
    }

    @SneakyThrows
    @SuppressWarnings("all")
    private String readFileContent(String filePath) {
        InputStream inputStream = ResourceReader.class.getClassLoader().getResourceAsStream(filePath);
        if (inputStream != null) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
                String line;
                StringBuilder builder = new StringBuilder();
                while ((line = reader.readLine()) != null) {
                    builder.append("\n").append(line);
                }
                return builder.toString();
            } catch (IOException e) {
                throw new ServiceException(filePath + "文件读取失败...");
            }
        }
        throw new ServiceException(filePath + "文件不存在...");
    }


    /**
     * 解析获取平台证书-https://api.mch.weixin.qq.com/v3/certificates
     *
     * @param associatedData associatedData
     * @param nonce          随机字符串
     * @param cipherText     加密
     * @param apiv3Key       apiv3Key
     * @return 微信平台公钥
     */
    @SneakyThrows
    public static String decryptCertSN(String associatedData, String nonce, String cipherText, String apiv3Key) {
        final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        SecretKeySpec key = new SecretKeySpec(apiv3Key.getBytes(), "AES");
        GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, key, spec);
        cipher.updateAAD(associatedData.getBytes());
        return new String(cipher.doFinal(java.util.Base64.getDecoder().decode(cipherText)));
    }

    /**
     * 解密
     *
     * @param ciphertext 密文
     * @param privateKey privateKey
     * @throws BadPaddingException BadPaddingException
     * @throws IOException         IOException
     */
    public static String rsaDecryptOAEP(String ciphertext, PrivateKey privateKey)
            throws BadPaddingException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            byte[] data = java.util.Base64.getDecoder().decode(ciphertext);
            return new String(cipher.doFinal(data), "utf-8");
        } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
            throw new ServiceException("当前Java环境不支持RSA v1.5/OAEP" + e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的私钥", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            throw new BadPaddingException("解密失败");
        }
    }


    /**
     * 微信平台证书公钥加密
     *
     * @param message     message
     * @param certificate certificate
     * @return 密文
     */
    @SneakyThrows
    public static String rsaEncryptOAEP(String message, X509Certificate certificate) {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
            byte[] data = message.getBytes(StandardCharsets.UTF_8);
            byte[] cipherdata = cipher.doFinal(data);
            return java.util.Base64.getEncoder().encodeToString(cipherdata);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new ServiceException("当前Java环境不支持RSA v1.5/OAEP" + e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的证书", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }

    /**
     * 获取证书。
     *
     * @param filename 证书文件路径  (required)
     * @return X509证书
     */
    @SneakyThrows
    public static X509Certificate getCertificate(String filename) {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filename));
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new ServiceException("证书已过期" + e);
        } catch (CertificateNotYetValidException e) {
            throw new ServiceException("证书尚未生效" + e);
        } catch (CertificateException e) {
            throw new ServiceException("无效的证书文件" + e);
        } finally {
            bis.close();
        }
    }

    public static void main(String[] args) {
        InputStream inputStream = ResourceReader.class.getClassLoader().getResourceAsStream("merchant_apiclient_key.pem");
        if (inputStream != null) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
                String line;
                StringBuilder builder = new StringBuilder();
                while ((line = reader.readLine()) != null) {
                    builder.append("\n").append(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
